package com.fast.framework.config.security;

import cn.hutool.core.util.ObjUtil;
import com.fast.framework.config.security.customize.AccessDeniedHandler;
import com.fast.framework.config.security.customize.AuthenticationEntryPoint;
import com.fast.framework.config.security.customize.CustomFilter;
import com.fast.framework.config.security.customize.CustomUrlDecisionManager;
import com.fast.framework.config.security.handle.LogoutSuccessHandlerImpl;
import com.fast.framework.config.security.jwt.JwtTAuthenticationTokenFilter;
import com.fast.framework.core.domain.User;
import com.fast.framework.web.service.RoleService;
import com.fast.framework.web.service.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;

import javax.annotation.Resource;

/**
 * <p>
 * Security配置类
 * </p>
 *
 * @author lanyuankeji
 */

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private AccessDeniedHandler accessDeniedHandler;
    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Resource
    private LogoutSuccessHandlerImpl logoutSuccessHandler;
    @Resource
    private JwtTAuthenticationTokenFilter jwtTAuthenticationTokenFilter;
    @Resource
    private CustomFilter customFilter;
    @Resource
    private CorsFilter corsFilter;
    @Resource
    private CustomUrlDecisionManager customUrlDecisionManager;
    @Resource
    private UserDetailsServiceImpl userService;
    @Resource
    private RoleService roleService;

    /**
     * 解决 无法直接注入 AuthenticationManager
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 这些资源在访问时不会被Spring Security进行安全校验，而是直接放行
     */
    @Override
    public void configure(WebSecurity web) {
        // 将不需要校验的路径放行，避免JWT请求校验
        web.ignoring().antMatchers(
                "/css/**",               // CSS 文件的路径
                "/js/**",                // JavaScript 文件的路径
                "favicon.ico",           // 网站图标文件的路径
                "/**.html",
                "/favicon.ico",
                "/webjars/**",           // Swagger 文档页面所需资源的路径
                "/swagger-resources/**", // Swagger 资源的路径
                "/v2/api-docs/**",       // Swagger API 文档的路径
                "/login", "/register", "/captcha/getCaptcha"
        );
    }

    /**
     * 配置 HTTP 安全策略
     *
     * @param http HTTP 安全对象
     * @throws Exception 配置异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //注解标记允许匿名访问的url

        // 禁用 CSRF 保护，因为使用了 JWT，不需要 CSRF
        http.csrf().disable()
                // 禁用HTTP响应标头
                .headers().cacheControl().disable().and()
                // 基于 Token，不使用 Session 进行身份验证管理
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 过滤请求 - 全部需要鉴权认证
                .authorizeRequests().anyRequest().authenticated()
                //动态权限配置
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        // 设置自定义的访问决策管理器
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        // 设置自定义的安全元数据源
                        object.setSecurityMetadataSource(customFilter);
                        return object;
                    }
                })
                // 禁用缓存
                .and().headers().cacheControl();

        // 添加Logout filter
        http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);

        //添加jwt登录授权过滤器：用于处理 JWT 的认证和授权逻辑。
        http.addFilterBefore(jwtTAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        http.addFilterBefore(corsFilter, JwtTAuthenticationTokenFilter.class);
        http.addFilterBefore(corsFilter, LogoutFilter.class);
        //添加自定义未授权和未登录返回：用于处理访问未授权资源和未登录状态时的异常情况。
        // 配置授权失败的处理器
        // 配置认证失败的处理器
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint);
    }

    /**
     * 创建密码加密器 Bean
     *
     * @return 返回 BCryptPasswordEncoder 的实例，用于加密密码
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 返回 BCryptPasswordEncoder 的实例，用于加密密码
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义用户详情服务实现类，用于加载用户信息以进行身份验证
     * 作用和功能是为 Spring Security 提供一个能够根据用户名加载用户信息的实现
     *
     * @return 返回一个匿名实现的 UserDetailsService，用于根据用户名加载用户信息
     */
    @Override
    @Bean
    public UserDetailsService userDetailsService() {

        // 返回一个匿名实现的 UserDetailsService，用于根据用户名加载用户信息
        return username -> {
            // 根据用户名从用户服务中获取登录用户信息
            User loginUserByUsername = userService.loadUserByUsername(username);

            // 如果获取的用户信息为空，则返回 null
            if (ObjUtil.isEmpty(loginUserByUsername)) throw new UsernameNotFoundException("用户名或密码不正确");
            loginUserByUsername.setRoles(roleService.getRoles(loginUserByUsername.getUserId()));

            // 否则返回获取的登录用户信息
            return loginUserByUsername;
        };
    }

}
